"""Admin classes for django-slots.

These classes are to be used in an application's admin.py, when
django-slots is being used.  They provide a way to set up a schedule of
transition objects in the normal django admin UI.  It is implemented as
an inline, which appears as a regular ``raw_id_fields``-style foreign
key despite being a GenericForeignKey.

Usage is as follows

    ###########
    # models.py
    ###########
    class MyModel(models.Model):
        ...
        schedule = generic.GenericRelation('slots.Transition')
        ...

    ##########
    # admin.py
    ##########
    from slots.admin import TransitionInline

    class MyModelAdmin(admin.ModelAdmin):
        ...
        # to see all transitions inline
        inlines = [TransitionInline]
        ...

    # or

    # to see only the current and future transitions inline
    from slots.admin import FutureTransitionInline

    class MyModelAdmin(admin.ModelAdmin):
        ...
        inlines = [FutureTransitionInline]
        ...

"""

import datetime

from django import forms
from django.http import HttpResponse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.contrib import admin, contenttypes
# this is imported so we can construct a ForeignKeyRawIdWidget from a generic
# foreign key ID field 
from django.db.models.fields.related import ForeignKey
# this is imported so we can override the default query set for inlines, 
# to apply custom filters and ordering to them
from django.contrib.contenttypes.generic import BaseGenericInlineFormSet

from slots.models import Transition

class ForeignKeyRawIdEditWidget(admin.widgets.ForeignKeyRawIdWidget):
    def render(self, name, value, attrs=None):
        output = super(ForeignKeyRawIdEditWidget, self).render(name, value,
                                                               attrs)
        # we want the normal output from the raw id widget, and are adding a
        # link to _edit_ the target object as well
        if value:
            related_url = '../../../%s/%s/%s/' % \
                    (self.rel.to._meta.app_label,
                    self.rel.to._meta.object_name.lower(),
                    value)
            output = output + "<a href='%s' class='changelink'>Edit content</a>" % related_url
        return mark_safe(output)

class TransitionForm(forms.ModelForm):
    """Form for Transition.

    This is the form to be used when you want to inline a particular
    type of object against another.  For example, if you want to inline
    'Sponsor' against 'Page', you would do this:

        from django.contrib.contenttypes.models import ContentType

        sponsor_type = ContentType.objects.get_for_model(Sponsor)

        class SponsorForm(TransitionForm):
            link_to_type = sponsor_type

    By doing this, the ``referring_model`` is enforced, so that it doesn't
    need to appear in the inline.  If you want a single set of inlines to
    manage all relationships, do not use this class.  If you want to group
    your inlines according to their type, use this class multiple times.

    """
    class Meta:
        model = Transition
    def clean(self):
        # we _insert_ the data here, because it's not in the form itself
        # as defined by the fieldset in TransitionInline.  (ie, we can't have a
        # clean_referring_model method as it would never be called).
        #
        # At the db level, Transition can have anything as
        # referenced_model and referring_model, because they are both
        # (used in) generic foreign keys.  This class is here to allow
        # the inlining of a specific type of object.
        #
        # referenced_model is dealt with for us by the use of
        # GenericRelation, and the ct_* fields.  See the models documentation
        # for GenericRelation usage.
        #
        # clean is the most convenient place to insert this data
        # NB it has to be the actual content type, not the ID
        self.cleaned_data['referring_model']=self.link_to_type
        return self.cleaned_data
        
class TransitionInlineFormSet(BaseGenericInlineFormSet):
    """Inline formset to show relationships with queryset using order_by"""
    def get_queryset(self):
        """Return a queryset of Transition objects in ascending time order"""
        qs = super(TransitionInlineFormSet, self).get_queryset()
        return qs.order_by('change_time')

class FutureTransitionInlineFormSet(BaseGenericInlineFormSet):
    """Inline formset to only show current+future relationships.

    By using this formset, all relationships between two objects which
    occurred in the past are hidden from the admin interface.
    """
    def get_queryset(self):
        """Return a queryset of Transition objects from now() onwards"""
        qs = super(FutureTransitionInlineFormSet, self).get_queryset()
        now = datetime.datetime.now()
        future = qs.order_by('change_time').exclude(change_time__lte=now)
        try:
            current = qs.filter(change_time__lte=now).order_by('-change_time')[0]
            # bit of a hack, to combine querysets we need the actual pod
            # (as above) as a single element query set
            current_qs = qs.filter(pk=current.pk)
        except IndexError:
            # if [0] failed, we have nothing currently set, so just return the
            # future (which of course may also be empty)
            return future
        else:
            # because these are _sets_, not lists, we need to use | and not &
            # 
            # ``future`` will never contain ``current_qs`` so &,
            # the intersection, would be empty.  | is the union.
            # 
            # we're relying on the union being ordered, so that ``current_qs``
            # appears first.
            #
            # the end result is a queryset of Transitions consisting
            # of the one whose change_time is nearest to, but before,
            # right now AND all those whose change_time is in the future
            return current_qs | future

class TransitionInline(contenttypes.generic.GenericTabularInline):
    """Inline for transitions.

    This class represents a schedule of foreign key relationships.

    """
    model = Transition
    ct_field = 'referenced_model'
    ct_fk_field = 'referenced_id'
    form = TransitionForm
    formset = TransitionInlineFormSet
    extra = 1
    # only show the referring_id, change_time, and notes
    # referenced_model and referenced_id (the pairing which consist of the
    # generic foreign key back to the 'master' object) are dealt with for us
    # because the model should have a GenericRelation to Transition.
    # referring_model is enforced by using TransitionForm, subclassed to include
    # link_to_type -- a ContentType, essentially a "limit_choices_to"-style
    # declaration
    # 
    # as for change_time and notes, without them this would be pretty
    # hopeless ;-)
    fieldsets = ( 
        ('All', {
            'fields': ('referring_id', 'change_time', 'notes')
        }),
    )
    # this is from django.db.models.fields.related
    # we use it because it's the type of object the raw_id_field widget needs
    @property
    def referring_id_helper(self):
        return ForeignKey(self.link_to).rel
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(TransitionInline, self).formfield_for_dbfield(db_field,
                                                                  **kwargs)
        if db_field.name == 'referring_id':
            field.widget = ForeignKeyRawIdEditWidget(self.referring_id_helper)
        return field

class FutureTransitionInline(TransitionInline):
    """Inline to show only current and future transitions inline"""
    formset = FutureTransitionInlineFormSet


